﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;

namespace Dungeon_Dive
{
    /// <summary>
    /// Class for Dungeon Dive monsters.
    /// </summary>
    public class Monster : GameEntity
    {
        Game parentGame;
        LogicEngine parentLevel;
        Log log;
        Elements tokenDrop;     // type of elemental pickup dead monster leaves

        PythonEngine pyEngine;
        XMLEngine xmlEngine;

        List<String> aiScripts;

        public Vector3 TargetTile { get; set; }
        public Stack<Vector3> Path { get; set; }
        public Stack<String> AiState { get; set; }

        public int Speed { get; set; }

        TimeSpan timeSinceLastPathfind;
        TimeSpan timeStuck; //For how many cycles we haven't moved even though we have a valid path
        public TimeSpan AttackDelayTime { get; set; }
        public bool HasAttacked { get; set; }

        Elements Type { get; set; }

        SoundEffect walk;
        Queue<SoundEffectInstance> sounds;
        SoundEffectInstance currentSound;

        public Monster(Game game, LogicEngine level, string name, int strength) : base(game)
        {
            parentGame = game;
            parentLevel = level;

            log = (Log)game.Services.GetService(typeof(Log));
            pyEngine = (PythonEngine)game.Services.GetService(typeof(PythonEngine));
            xmlEngine = (XMLEngine)game.Services.GetService(typeof(XMLEngine));

            aiScripts = new List<string>();

            if (!loadFromXml(name + ".xml"))
                log.Write("Loading XML file failed!");

            this.CurrentAnim = "default";
            Scale = 1.0f;
            Rotation = 0.0f;

            CreateBoundingSphere();

            if (strength > 1)   // monsters have (strength - 1) tokens of their type...
            {
                for (int i = 0; i < strength - 1; ++i)
                    AddToken(Type);
            }
            AddToken(RandomElement());  // ...and one randomly picked

            tokenDrop = PickTokenDrop();

            TargetTile = new Vector3(-1, -1, -1);
            Path = new Stack<Vector3>();
            AiState = new Stack<string>();
            for (int i = 0; i < aiScripts.Count; ++i)
            {
                if (i == 0) //Do this once, but only if we have aiScripts
                {
                    aiScripts.Add("move");
                    pyEngine.AddScriptFromFile("move", ".\\Scripts\\move.py");
                }

                if (aiScripts[i] != "pathfinding" && aiScripts[i] != "unstick")
                {
                    AiState.Push(aiScripts[i]);
                    break;
                }
            }

            timeSinceLastPathfind = TimeSpan.Zero;
            timeStuck = TimeSpan.Zero;
            AttackDelayTime = TimeSpan.Zero;

            Speed = 1;

            sounds = new Queue<SoundEffectInstance>();
        }

        private bool loadFromXml(String filename)
        {
            log.Write("loadFromXml(" + filename + ")");
            String anim;
            String elem = "";
            String ai;
            String file;

            if (!xmlEngine.OpenFile(".\\XML\\"+filename)) return false;

            log.Write("Loaded xml file succesfully.");

            while (elem != "monster") //Read until we find the monster(root) element
            {
                elem = xmlEngine.NextElement();
                log.Write("elem: " + elem);
            }

            elem = xmlEngine.NextElement(); //Get the first element after monster

            while (elem != "monster") //Loop until the closing element
            {
                log.Write("elem: "+elem);
                switch(elem)
                {
                    case "animation":
                        anim = xmlEngine.GetAttribute("name");
                        elem = xmlEngine.NextElement();
                        while (elem != "animation") //Loop until we find the closing animation element
                        {
                            log.Write("elem: " + elem);
                            if (elem == "frame")
                            {
                                log.Write("attrib: " + xmlEngine.GetAttribute("model"));
                                this.AddFrame(anim,
                                    parentGame.Content.Load<Model>(".\\Models\\" + xmlEngine.GetAttribute("model")));
                            }
                            elem = xmlEngine.NextElement();
                        }
                        break;

                    case "ai":
                        ai = xmlEngine.GetAttribute("name");
                        file = xmlEngine.GetAttribute("script");
                        pyEngine.AddScriptFromFile(ai, ".\\Scripts\\" + file);
                        aiScripts.Add(ai);
                        break;

                    case "tokens":
                        elem = xmlEngine.NextElement();
                        while (elem != "tokens") //Loop until we find the closing animation element
                        {
                            log.Write("elem: " + elem);
                            if (elem == "token")
                            {
                                log.Write("attrib: " + xmlEngine.GetAttribute("type"));
                                string type = xmlEngine.GetAttribute("type");
                                switch (type)
                                {
                                    case "earth":                                        
                                        Type = Elements.Earth;
                                        break;
                                    case "water":                                        
                                        Type = Elements.Water;
                                        break;
                                    case "air":                                        
                                        Type = Elements.Air;
                                        break;
                                    case "fire":                                        
                                        Type = Elements.Fire;
                                        break;
                                }
                            }
                            elem = xmlEngine.NextElement();
                        }
                        break;

                    case "sound":
                        if (xmlEngine.GetAttribute("name") == "walk")
                        {
                            walk = parentGame.Content.Load<SoundEffect>("Audio\\" +
                                xmlEngine.GetAttribute("file"));
                        }
                        break;

                }
                elem = xmlEngine.NextElement();
            }
            xmlEngine.CloseFile();
            return true;
        }

        public Elements GetElement()
        {
            return tokenDrop;
        }

        private Elements PickTokenDrop()
        {
            int i = rand.Next(tokens.Count);
            Elements drop = tokens.ElementAt<Elements>(i);
            return drop;
        }

        public override void Update(GameTime gameTime)
        {
            if (TargetTile.X == -1) //Invalid, so make target = position
            {
                TargetTile = new Vector3(TilePosition.X, 0, TilePosition.Z);
            }

            if (aiScripts.Count > 0) //Only attempt to run ai scripts if we actually have some
            {
                timeSinceLastPathfind += gameTime.ElapsedGameTime;
                AttackDelayTime += gameTime.ElapsedGameTime;

                pyEngine.SetVariable("thisMonster", (Monster)this); //Make 'thisMonster' point to this

                pyEngine.RunScript(AiState.Peek());

                if (!Move() && CurrentAnim != "attack") timeStuck += gameTime.ElapsedGameTime;

                if (timeStuck > TimeSpan.FromMilliseconds(1000))
                {
                    timeStuck -= TimeSpan.FromMilliseconds(1000);
                    AiState.Push("unstick");
                }
            }

            if (currentSound == null && sounds.Count > 0)
            {
                currentSound = sounds.Dequeue();
                currentSound.Play();
                float volume = 500 / Vector3.Distance(Position, parentLevel.TPC.Position);
                if (volume > 1.0f) volume = 1.0f;
                if (volume < 0f) volume = 0f;
                currentSound.Volume = volume;
            }

            if (currentSound != null)
            {
                if (currentSound.State == SoundState.Stopped)
                {
                    currentSound = null;
                }
            }

            base.Update(gameTime);
        }

        private bool Move()
        {
            if (TilePosition != TargetTile)
            {
                //Move
                if (walk != null)
                {
                    if (FrameCount("walk") % 2 == 0 && sounds.Count < 2) sounds.Enqueue(walk.CreateInstance());
                }
                
                bool wallCollisionX = true;
                bool wallCollisionZ = true;

                Vector3 oldPos = Position;
                Vector3 newPosX = new Vector3(Position.X, Position.Y, Position.Z);
                Vector3 newPosZ = new Vector3(Position.X, Position.Y, Position.Z);

                Rotation = (float)Math.Atan2(((TargetTile.Z * 200) - Position.Z)*-1, (TargetTile.X * 200) - Position.X);
                Rotation += MathHelper.PiOver2;

                if (TilePosition.X < TargetTile.X)
                {
                    newPosX.X += Speed;
                }
                else if (TilePosition.X > TargetTile.X)
                {
                    newPosX.X -= Speed;
                }

                if (TilePosition.Z < TargetTile.Z)
                {
                    newPosZ.Z += Speed;
                }
                else if (TilePosition.Z > TargetTile.Z)
                {
                    newPosZ.Z -= Speed;
                }

                Position = new Vector3(newPosX.X, newPosX.Y, newPosZ.Z);

                foreach (GameEntity NPC in parentLevel.Entities)
                {
                    if (NPC != this)
                    {
                        if (parentLevel.CharacterCollision(this, NPC))
                        {
                            Position = oldPos;
                            return false;
                        }
                    }
                }

                if (parentLevel.CharacterCollision(this, parentLevel.TPC))
                {
                    Position = oldPos;
                    return false;
                }

                if (newPosX.X != oldPos.X) //We're actually attempting to move on the X-axis
                {
                    Position = newPosX;
                    wallCollisionX = parentLevel.WallCollision(this);
                }
                if (newPosZ.Z != oldPos.Z)
                {
                    Position = newPosZ;
                    wallCollisionZ = parentLevel.WallCollision(this);
                }

                if (!wallCollisionX && !wallCollisionZ)
                {
                    Position = new Vector3(newPosX.X, newPosX.Y, newPosZ.Z);
                    return true;
                }
                else if (!wallCollisionX)
                {
                    Position = newPosX;
                    return true;
                }
                else if (!wallCollisionZ)
                {
                    Position = newPosZ;
                    return true;
                }

                Position = oldPos;
                return false;
            }
            else if (Path.Count > 0) //Not at the target and we still have pathing info left
            {
                TargetTile = Path.Pop();
            }

            return true;
        }
    }
}
